/*******************************************************************************
 * Copyright (c) 2005, 2014 springside.github.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 *******************************************************************************/
package im.redis.client;

import im.redis.client.operations.HashOperation;
import im.redis.client.operations.ListOperation;
import im.redis.client.operations.PubSubOperation;
import im.redis.client.operations.SetOperation;
import im.redis.client.operations.SortedSetOperation;
import im.redis.client.operations.StringOperation;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.jedis.exceptions.JedisException;

/**
 * JedisTemplate 提供了一个template方法，负责对Jedis连接的获取与归还。 JedisAction<T> 和
 * JedisActionNoResult两种回调接口，适用于有无返回值两种情况。 PipelineAction 与
 * PipelineActionResult两种接口，适合于pipeline中批量传输命令的情况。
 * 
 * 同时提供一些JedisOperation中定义的 最常用函数的封装, 如get/set/zadd等。
 */
public class JedisTemplate {

	private static Logger logger = LoggerFactory.getLogger(JedisTemplate.class);

	private JedisPool jedisPool;

	public JedisTemplate(JedisPool jedisPool) {
		this.jedisPool = jedisPool;
	}

	/**
	 * Callback interface for template.
	 */
	public interface JedisAction<T> {
		T action(Jedis jedis);
	}

	/**
	 * Callback interface for template without result.
	 */
	public interface JedisActionNoResult {
		void action(Jedis jedis);
	}

	/**
	 * Callback interface for template.
	 */
	public interface PipelineAction {
		List<Object> action(Pipeline Pipeline);
	}

	/**
	 * Callback interface for template without result.
	 */
	public interface PipelineActionNoResult {
		void action(Pipeline Pipeline);
	}

	/**
	 * Execute with a call back action with result.
	 */
	public <T> T execute(JedisAction<T> jedisAction) throws JedisException {
		Jedis jedis = null;
		boolean broken = false;
		try {
			jedis = jedisPool.getResource();
			return jedisAction.action(jedis);
		} catch (JedisException e) {
			broken = handleJedisException(e);
			throw e;
		} finally {
			closeResource(jedis, broken);
		}
	}

	/**
	 * Execute with a call back action without result.
	 */
	public void execute(JedisActionNoResult jedisAction) throws JedisException {
		Jedis jedis = null;
		boolean broken = false;
		try {
			jedis = jedisPool.getResource();
			jedisAction.action(jedis);
		} catch (JedisException e) {
			broken = handleJedisException(e);
			throw e;
		} finally {
			closeResource(jedis, broken);
		}
	}

	/**
	 * Execute with a call back action with result in pipeline.
	 */
	public List<Object> execute(PipelineAction pipelineAction) throws JedisException {
		Jedis jedis = null;
		boolean broken = false;
		try {
			jedis = jedisPool.getResource();
			Pipeline pipeline = jedis.pipelined();
			pipelineAction.action(pipeline);
			return pipeline.syncAndReturnAll();
		} catch (JedisException e) {
			broken = handleJedisException(e);
			throw e;
		} finally {
			closeResource(jedis, broken);
		}
	}

	/**
	 * Execute with a call back action without result in pipeline.
	 */
	public void execute(PipelineActionNoResult pipelineAction) throws JedisException {
		Jedis jedis = null;
		boolean broken = false;
		try {
			jedis = jedisPool.getResource();
			Pipeline pipeline = jedis.pipelined();
			pipelineAction.action(pipeline);
			pipeline.sync();
		} catch (JedisException e) {
			broken = handleJedisException(e);
			throw e;
		} finally {
			closeResource(jedis, broken);
		}
	}

	/**
	 * Return the internal JedisPool.
	 */
	public JedisPool getJedisPool() {
		return jedisPool;
	}

	/**
	 * Handle jedisException, write log and return whether the connection is
	 * broken.
	 */
	protected boolean handleJedisException(JedisException jedisException) {
		if (jedisException instanceof JedisConnectionException) {
			logger.error("Redis connection lost.", jedisException);
		} else if (jedisException instanceof JedisDataException) {
			if ((jedisException.getMessage() != null) && (jedisException.getMessage().indexOf("READONLY") != -1)) {
				logger.error("Redis connection are read-only slave.", jedisException);
			} else {
				// dataException, isBroken=false
				return false;
			}
		} else {
			logger.error("Jedis exception happen.", jedisException);
		}
		return true;
	}

	/**
	 * Return jedis connection to the pool, call different return methods
	 * depends on the conectionBroken status.
	 */
	protected void closeResource(Jedis jedis, boolean conectionBroken) {
		try {
			if (jedis != null) {
				jedis.close();
			}
		} catch (Exception e) {
			logger.error("return back jedis failed, will force close the jedis.", e);
			JedisUtils.destroyJedis(jedis);
		}
	}

	// / Common Actions ///

	/**
	 * Remove the specified keys. If a given key does not exist no operation is
	 * performed for this key.
	 * 
	 * return false if one of the key is not exist.
	 */
	public Boolean del(final String... keys) {
		return execute(new JedisAction<Boolean>() {

			@Override
			public Boolean action(Jedis jedis) {
				return jedis.del(keys) == keys.length ? true : false;
			}
		});
	}

	public Boolean expire(final String key, final int seconds) {
		return execute(new JedisAction<Boolean>() {

			@Override
			public Boolean action(Jedis jedis) {
				return jedis.expire(key, seconds) == 1;
			}
		});
	}

	public String eval(final String script, final List<String> keys, final List<String> args) {
		return execute(new JedisAction<String>() {
			@Override
			public String action(Jedis jedis) {
				Object result = jedis.eval(script, keys, args);
				return result == null ? null : result.toString();
			}
		});
	}

	public boolean ping() {
		return JedisUtils.ping(jedisPool);
	}

	public void flushDB() {
		execute(new JedisActionNoResult() {

			@Override
			public void action(Jedis jedis) {
				jedis.flushDB();
			}
		});
	}

	public StringOperation stringOps() {
		return new StringOperation(this);
	}

	public HashOperation hashOps() {
		return new HashOperation(this);
	}

	public ListOperation listOps() {
		return new ListOperation(this);
	}

	public SetOperation setOps() {
		return new SetOperation(this);
	}

	public SortedSetOperation sortedSetOps() {
		return new SortedSetOperation(this);
	}

	public PubSubOperation pubSubOps() {
		return new PubSubOperation(this);
	}
}
